package com.springmvc.servlet;

import com.springmvc.annotation.Controller;
import com.springmvc.annotation.RequestMapping;
import com.springmvc.annotation.RequestParm;
import com.springmvc.context.WebApplicationContext;
import com.springmvc.exception.ContextException;
import com.springmvc.handler.MyHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.plaf.synth.SynthOptionPaneUI;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
*@Author Lao Wang
*@Date 2023/11/2
*@Description 模拟官方springmvc中的核心控制器dispatcher
*
**/
public class DispatcherServlet extends HttpServlet {
    private WebApplicationContext webApplicationContext;
    //存储URL和对象方法的映射关系
    List<MyHandler> handlerList =new ArrayList<>();
    @Override
    public void init() throws ServletException {
        //1.servlet初始化的时候，读取初始化的参数
        String contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");
        //2.创建spring容器
        webApplicationContext = new WebApplicationContext(contextConfigLocation);
        //初始化spring容器
        webApplicationContext.refresh();
        //4.初始化请求映射 /user/query->controller->method->parameter
        initHandlerMapping();
        System.out.println("请求地址和控制器方法的映射关系："+handlerList);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //进行请求分发
        excuteDispatch(req,resp);
    }

    //初始化请求映射
    public void initHandlerMapping(){
        //判断IOCmap中是否有bean对象
        if (webApplicationContext.iocMap.isEmpty()) {
            throw new ContextException("Spring容器为空");
        }
        for (Map.Entry<String, Object> entry :webApplicationContext.iocMap.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            //如果类上有controller注解则代表它是控制层
            if (clazz.isAnnotationPresent(Controller.class)) {
                //取出控制层中的所有方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    //方法上有没有requestMapping的注解，如果有就把它取出来
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMappingAnnotation = declaredMethod.getAnnotation(RequestMapping.class);
                        // 取到/user/query
                        String url = requestMappingAnnotation.value();
                        MyHandler myHandler = new MyHandler(url,entry.getValue(),declaredMethod);
                        handlerList.add(myHandler);
                    }
                }
            }
        }
    }
    //请求分发
    public void excuteDispatch(HttpServletRequest req, HttpServletResponse resp){
        MyHandler handler = getHandler(req);
        try {
            if (handler == null) {
                resp.getWriter().print("<h1>404 NOT FOUND!</h1>");
            }else {
                Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
                //定义一个参数数组
                Object[] params = new Object[parameterTypes.length];
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())){
                        params[i]=req;
                    }else if ("HttpServletResponse".equals(parameterType.getSimpleName())){
                        params[i]=resp;
                    }
                }
                //获取请求中的参数key,value
                Map<String, String[]> parameterMap = req.getParameterMap();
                for (Map.Entry<String, String[]> entry:parameterMap.entrySet()){
                    String name = entry.getKey();
                    String value = entry.getValue()[0];//name参数

                    int index = hasRequestParm(handler.getMethod(),name);
                    if(index!=-1){
                        params[index] = value;
                    }else {//如果没有注解，则直接取参数名字
                        List<String> names = getparameterNames(handler.getMethod());
                        //System.out.println(names);
                        for (int i = 0; i < names.size(); i++) {
                            if (name.equals(names.get(i)) ) {//url中的参数名与controller方法中的参数名比较
                                params[i] = value;//参数名相同则将其值取出来
                                break;
                            }
                        }
                    }
                }

                //调用控制器中的方法
                handler.getMethod().invoke(handler.getController(),params);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    //根据请求找到对应的controller
    public MyHandler getHandler(HttpServletRequest req){
        // /user/query
        String requestURI = req.getRequestURI();
        for (MyHandler myHandler:handlerList){
            //这里因为我的idea中tomcat配置与视频不一样，所以单独处理一下
            String myHandlerUrl = "/MySpringMvc_war"+myHandler.getUrl();
            if (myHandlerUrl.equals(requestURI)){
                return myHandler;
            }
        }
        return null;
    }

    /**
     * 判断控制器方法的参数是否有RequestParm的注解，如果有则找到对应注解的value值(参数值)，且返回参数下标，没有找到则返回-1
     * @param method
     * @param name
     * @return
     */
    public int hasRequestParm(Method method,String name){
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            boolean b = p.isAnnotationPresent(RequestParm.class);
            if(b){
                RequestParm requestParm = p.getAnnotation(RequestParm.class);
                String requestParmValue = requestParm.value();
                if(name.equals(requestParmValue)){
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 获取控制器方法的参数名字
     * @param method
     * @return
     */
    public List<String> getparameterNames(Method method){
        List<String> list = new ArrayList<String>();
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            list.add(parameter.getName());
        }
        return list;
    }
}
